import { isEqual } from 'lodash'
import { APP_STATUS, RECORDER_STATUS, INSPECTOR_STATUS, PLAYER_STATUS, PLAYER_MODE, } from '@/common/constant';
import { setIn, updateIn, compose, pick, partial } from '@/common/utils'
import ActionTypes from '@/actions/action-types'
import { safeUpdateIn } from '@/common/ts_utils'
import { MacroExtraData } from '@/types/macro_extra_data'
import { toJSONString } from '@/common/convert_utils'
import { getCurrentMacroId, getShouldIgnoreTargetOptions } from '@/recomputed'
import { normalizeCommand, normalizeTestCase } from '@/common/test_case_model'

type TestSuiteCase = {
    testCaseId: string;
    loops: string | number;
}

type TestSuiteStatus = {
    doneIds?: string[];
    errorIds?: string[];
    currentIndex?: number;
    isPlaying?: boolean;
}

type TestSuite = {
    name: string;
    id?: string;
    fold?: boolean;
    cases: TestSuiteCase[];
    playStatus: TestSuiteStatus;
}

type Command = {
    id: string;
    cmd: string;
    target: string;
    value: string;
    extra?: any;
    targetOptions?: string[];
    description?: string;
    children?: Command[]
}

type Entry = {
    dir: string;
    name: string;
    relativePath: string;
    fullPath: string;
    size: number;
    isFile: boolean;
    isDirectory: boolean;
    lastModified: Date;
}

type Tree<T> = T & {
    children: Tree<T>[];
}

export type EntryNode = Tree<Entry>

enum ProxyScheme {
    Http = 'http',
    Https = 'https',
    Socks4 = 'socks4',
    Socks5 = 'socks5'
}

type ProxyData = {
    type: ProxyScheme;
    host: string;
    port: string;
    username?: string;
    password?: string;
}

type MacroCommand = {
    cmd: string;
    target?: string;
    value?: string;
    targetOptions?: string[];
}

type Macro = {
    name: string;
    commands: MacroCommand[];
    status?: string;
    id?: string;
}

export type MacroInState = {
    id: string;
    name: string;
    path: string;
    data: {
        commands: Command[];
    };
}

export type FolderExtraData = {
    folded: boolean;
}

type ID = string;

type Resource = {
    url: string;
    name: string;
    createTime: Date;
}

type Editing = {
    commands: Command[];
    meta: {
        src: null | {
            id: ID;
            name: string;
        };
        hasUnsaved: boolean;
        selectedIndex: number;
        indexToInsertRecorded?: number;
    };
}

export enum RunBy {
    Html = 'html',
    Bookmark = 'bookmark',
    Manual = 'manual'
}

export function stringForRunBy(type: RunBy): string {
    switch (type) {
        case RunBy.Bookmark:
            return 'bookmark'

        case RunBy.Html:
            return 'command line'

        case RunBy.Manual:
            return 'manual'
    }
}

export type LogType = 'status' | 'reflect' | 'echo' | 'info' | 'warning' | 'error' | 'report'

export type LogSource = {
    macroId: string;
    macroName: string;
    commandIndex: number;
    isSubroutine?: boolean;
}

export type LogItem = {
    type: LogType;
    text: string;
    id: string;
    stack: LogSource[];
    createTime: Date;
    options: Record<string, any>;
}

export type UIConfig = {
    newPreinstallVersion?: boolean;
    showSettings?: boolean;
    settingsTab?: string;
    sidebarTab?: 'Macro';
    showWebsiteWhiteList?: boolean;
    showXFileNotInstalledDialog?: boolean;
    focusArea?: FocusArea;
    isSaving?: boolean;
}

export enum FocusArea {
    Unknown = 'unknown',
    Sidebar = 'sidebar',
    CommandTable = 'command_table',
    CodeSource = 'code_source'
}

export const newTestCaseEditing: Editing = {
    commands: [],
    meta: {
        src: null,
        hasUnsaved: true,
        selectedIndex: -1
    }
}

type AppStatus = typeof APP_STATUS[keyof typeof APP_STATUS]

// 定义 store 的类型
export type State = {
    count: number; // test property
    config: Record<string, any>;
    status: AppStatus;
    recorderStatus: string;
    inspectorStatus: string;
    isLoadingMacros: boolean;
    from: RunBy;
    noDisplayInPlay: boolean;
    ocrInDesktopMode: boolean;
    replaySpeedOverrideToFastMode: boolean;
    editor: {
        testSuites: TestSuite[];
        testCases: MacroInState[];
        macrosExtra: Record<ID, FolderExtraData | MacroExtraData>;
        macroFolderStructure: EntryNode[];
        testSuitesExtra: Record<ID, any>;
        currentMacro: Macro | null;
        editing: Editing;
        editingSource: {
            original: string | null; // 已保存到文件
            pure: string | null; // 编辑前
            current: string | null; // 编辑后
            error: string | null; // 错误
        };
        clipboard: {
            commands: Command[];
        }; // 剪切复制的命令
        activeTab: 'table_view' | 'source_view';
        isDraggingCommand: boolean;
    };
    player: {
        mode: string;
        status: string;
        stopReason: string | null;
        currentLoop: number;
        loops: number;
        nextCommandIndex: number | null;
        playInterval: number;
        timeoutStatus: {
            type: string | null;
            total: number | null;
            past: number | null;
        };
    };
    recorder: {
        skipOpen: boolean;
    };
    variables: Array<{ key: string; value: any; }>;
    logs: LogItem[];
    csvs: Array<Resource & { size: number }>;
    visions: Resource[];
    screenshots: Resource[];
    proxy: ProxyData | null;
    ui: UIConfig;
    macroQuery: string;
}

// 初始化 store 的值
const initialState: State = {
    count: 0, // test value
    status: APP_STATUS.NORMAL, // 应用状态
    recorderStatus: RECORDER_STATUS.STOPPED, // 录制器状态
    inspectorStatus: INSPECTOR_STATUS.STOPPED, // 检查器状态
    isLoadingMacros: false,
    from: RunBy.Manual,
    noDisplayInPlay: false,
    ocrInDesktopMode: false,
    replaySpeedOverrideToFastMode: false,
    editor: {
        testSuites: [], // 过时，不再使用
        testCases: [],// 过时，不再使用
        currentMacro: null, // 当前选择的实例，当编辑时修改editing，保存时更新currentMacro
        // macrosExtra is used to store:
        // * status
        // * breakpoints
        // * doneCommandIndices
        // * errorCommandIndices
        // * warningCommandIndices
        macrosExtra: {},
        // `macroFolderStructure` just holds folder and path info,
        // while macro content is still in `testCases`.
        macroFolderStructure: [], // 目录结构
        testSuitesExtra: {},
        editing: {
            ...newTestCaseEditing
        }, // 编辑时实时更新，当点保存时更新currentMacro，与currentMacro比较可判断出是否编辑了
        editingSource: {
            // Saved version 已保存
            original: null,
            // Version before editing 编辑前
            pure: null,
            // Version keeping track of any editing 编辑后
            current: null,
            error: null
        },
        clipboard: {
            commands: []
        },
        activeTab: 'table_view', // 视图
        isDraggingCommand: false // 是否在拖拽命令
    },
    player: {
        mode: PLAYER_MODE.TEST_CASE, // 播放器模式
        status: PLAYER_STATUS.STOPPED, // 播放器状态
        stopReason: null,
        currentLoop: 0,
        loops: 0,
        nextCommandIndex: null,
        playInterval: 0,
        timeoutStatus: {
            type: null,
            total: null,
            past: null
        }
    },
    recorder: {
        skipOpen: false
    },
    variables: [],
    logs: [],
    screenshots: [],
    csvs: [],
    visions: [],
    config: {
        "showSidePanel": false, // 显示侧边栏
        "useDarkTheme": false, // 使用暗黑主题
        "sidePanelOnLeft": false, // 侧边栏在左侧
        "anthropicAPIKey": "261a1412798a37a8d33af6da6cc26da3:bGl3ZWlzdW1Ab3V0bG9vay5jb20=", // 算法模型key
        "aiComputerUseMaxLoops": 20, // AI最大次数
        "useInitialPromptInAiChat": true, // 
        "aiChatSidebarPrompt": "Describe what you see, in 10 words or less.",
        "showSettingsOnStart": false,
        "showSidebar": false,
        "showBottomArea": true,
        "playScrollElementsIntoView": true,
        "playHighlightElements": true,
        "playCommandInterval": 0.3,
        "saveAlternativeLocators": true,
        "recordNotification": true,
        "recordClickType": "click",
        "showTestCaseTab": false,
        "logFilter": "All",
        "onErrorInLoop": "continue_next_loop",
        "allowRunFromBookmark": true,
        "allowRunFromFileSchema": true,
        "allowRunFromHttpSchema": true,
        "timeoutPageLoad": 60,
        "timeoutElement": 10,
        "timeoutMacro": 0,
        "timeoutDownload": 60,
        "lastBackupActionTime": 1747979569779,
        "enableAutoBackup": true,
        "autoBackupInterval": 7,
        "autoBackupTestCases": true,
        "autoBackupTestSuites": true,
        "autoBackupScreenshots": true,
        "autoBackupCSVFiles": true,
        "autoBackupVisionImages": true,
        "shouldEncryptPassword": "master_password",
        "masterPassword": "",
        "showCommonInternalVariables": true,
        "showAdvancedInternalVariables": false,
        "storageMode": "browser",
        "xmodulesStatus": "unregistered",
        "ocrCalibration": 6,
        "ocrCalibration_internal": 6,
        "ocrScaling": 100,
        "ocrEngine": 98,
        "ocrMode": "enabled",
        "ocrLanguage": "eng",
        "ocrLanguageOption": [
            {
                "text": "Arabic",
                "value": "ara"
            },
            {
                "text": "ChineseSimplified",
                "value": "chs"
            },
            {
                "text": "ChineseTraditional",
                "value": "cht"
            },
            {
                "text": "Czech",
                "value": "ce"
            },
            {
                "text": "Danish",
                "value": "dan"
            },
            {
                "text": "Dutch",
                "value": "dut"
            },
            {
                "text": "English",
                "value": "eng"
            },
            {
                "text": "Finnish",
                "value": "fin"
            },
            {
                "text": "French",
                "value": "fre"
            },
            {
                "text": "German",
                "value": "ger"
            },
            {
                "text": "Greek",
                "value": "gre"
            },
            {
                "text": "Hungarian",
                "value": "hun"
            },
            {
                "text": "Italian",
                "value": "ita"
            },
            {
                "text": "Japanese",
                "value": "jpn"
            },
            {
                "text": "Korean",
                "value": "kor"
            },
            {
                "text": "Norwegian",
                "value": "nor"
            },
            {
                "text": "Polish",
                "value": "pol"
            },
            {
                "text": "Portuguese",
                "value": "por"
            },
            {
                "text": "Russian",
                "value": "rus"
            },
            {
                "text": "Spanish",
                "value": "spa"
            },
            {
                "text": "Swedish",
                "value": "swe"
            },
            {
                "text": "Turkish",
                "value": "tur"
            }
        ],
        "ocrOfflineURL": "",
        "ocrOfflineAPIKey": "",
        "cvScope": "browser",
        "defaultVisionSearchConfidence": 0.6,
        "useDesktopScreenCapture": true,
        "waitBeforeDesktopScreenCapture": false,
        "secondsBeforeDesktopScreenCapture": 3,
        "defaultProxy": "",
        "defaultProxyAuth": "",
        "turnOffProxyAfterReplay": true,
        "disableOpenSidepanelBtnTemporarily": false,
        "oneTimeShowSidePanel": null,
        "size": {
            "standard": {
                "height": 912,
                "width": 1707
            }
        },
        "websiteWhiteList": [
            "http://127.0.0.1:5500/ui.vision.html"
        ]
    },
    proxy: null,
    ui: {
        focusArea: FocusArea.Unknown
    },
    macroQuery: '',

}

const setEditingSourceCurrent = (state: State) => {
    const macro = {
        name: state.editor.editing.meta.src ? state.editor.editing.meta.src.name : 'Untitled',
        commands: state.editor.editing.commands
    }

    const str = toJSONString(macro, {
        ignoreTargetOptions: getShouldIgnoreTargetOptions(state)
    })
    return updateIn(['editor', 'editingSource'], (editingSource: any) => ({ ...editingSource, pure: str, current: str }), state)
}

const updateBreakpointIndices = (indices: number[], action: string, actionIndex: number | any[]) => {
    const handleSingleAction = (indices: number[], action: string, actionIndex: number) => {
        switch (action) {
            case 'add': {
                let result = indices.slice()

                for (let i = 0, len = indices.length; i < len; i++) {
                    if (result[i] >= actionIndex) {
                        result[i] += 1
                    }
                }

                return result
            }

            case 'delete': {
                let result = indices.slice()

                for (let i = indices.length - 1; i >= 0; i--) {
                    if (result[i] > actionIndex) {
                        result[i] -= 1
                    } else if (result[i] === actionIndex) {
                        result.splice(i, 1)
                    }
                }

                return result
            }

            default:
                throw new Error(`updateBreakpointIndices: unknown action, '${action}'`)
        }
    }

    if (typeof actionIndex === 'number') {
        return handleSingleAction(indices, action, actionIndex)
    }

    if (Array.isArray(actionIndex)) {
        // Note: sort action indices as desc.  Bigger indice will be handled earlier, so that it won't affect others
        const actionIndices = actionIndex.slice()
        actionIndices.sort((a, b) => b - a)

        return actionIndices.reduce((indices, actionIndex) => {
            return handleSingleAction(indices, action, actionIndex)
        }, indices)
    }

    throw new Error('updateBreakpointIndices: actionIndex should be either number or an array of number')
}

const saveEditingSourceCurrent = (state: State) => {
    const { current } = state.editor.editingSource
    return updateIn(['editor', 'editingSource'], (editingSource: any) => ({ ...editingSource, pure: current, original: current }), state)
}

const setEditingSourceOriginalAndPure = (macro: any, state: State) => {
    const str = toJSONString(macro, {
        ignoreTargetOptions: getShouldIgnoreTargetOptions(state)
    })
    return updateIn(['editor', 'editingSource'], (editingSource: any) => ({ ...editingSource, pure: str, original: str }), state)
}

const resetEditingSource = partial((macro, state) => {
    // log('resetEditingSource', macro)
    const str = toJSONString(macro, {
        ignoreTargetOptions: false
    })
    return setIn(
        ['editor', 'editingSource'],
        {
            original: str,
            pure: str,
            current: str,
            error: null
        },
        state
    )
})

// 编辑后，修改标识，避免实时计算，造成性能问题
const updateHasUnSaved = (state: State) => {
    const { meta, ...data } = state.editor.editing
    const id = meta.src && meta.src.id
    if (!id) return state

    const currentMacro: any = state.editor.currentMacro
    const normalizedEditing = normalizeTestCase({ data })
    const hasUnsaved = !isEqual(currentMacro && currentMacro.data, normalizedEditing.data)

    return setIn(['editor', 'editing', 'meta', 'hasUnsaved'], hasUnsaved, state)
}

export default function reducer(state = initialState, action: any) {
    switch (action.type) {
        // 开始录制
        case ActionTypes.START_RECORDING_SUCCESS: {
            return {
                ...state,
                status: APP_STATUS.RECORDER,
                recorderStatus: APP_STATUS.PENDING,
                player: {
                    ...state.player,
                    nextCommandIndex: null
                }
            }
        }
        // 停止录制
        case ActionTypes.STOP_RECORDING_SUCCESS: {
            return {
                ...state,
                status: APP_STATUS.NORMAL,
                recorderStatus: RECORDER_STATUS.STOPPED
            }
        }
        // 开始捕获
        case ActionTypes.START_INSPECTING_SUCCESS:
            return {
                ...state,
                status: APP_STATUS.INSPECTOR,
                inspectorStatus: INSPECTOR_STATUS.PENDING
            }
        // 停止捕获、捕获完成
        case ActionTypes.STOP_INSPECTING_SUCCESS:
        case ActionTypes.DONE_INSPECTING: {
            return {
                ...state,
                status: APP_STATUS.NORMAL,
                recorderStatus: INSPECTOR_STATUS.STOPPED
            }
        }
        // 开始播放
        case ActionTypes.START_PLAYING: {
            return {
                ...state,
                status: APP_STATUS.PLAYER
            }
        }
        // 停止播放
        case ActionTypes.STOP_PLAYING: {
            return {
                ...state,
                status: APP_STATUS.NORMAL
            }
        }
        // 新增命令
        case ActionTypes.APPEND_COMMAND: {
            return compose(
                setEditingSourceCurrent,
                updateHasUnSaved,
                updateIn(
                    ['editor', 'editing', 'commands'],
                    (commands: any[]) => [...commands, action.data.command]
                )
            )(state)
        }
        // 复制命令
        case ActionTypes.DUPLICATE_COMMAND: {
            return compose(
                setEditingSourceCurrent,
                updateHasUnSaved,
                setIn(
                    ['editor', 'editing', 'meta', 'selectedIndex'],
                    action.data.index + 1
                ),
                updateIn(
                    ['editor', 'editing', 'commands'],
                    (commands: any[]) => {
                        const { index } = action.data
                        const newCommands = commands.slice()
                        newCommands.splice(index + 1, 0, commands[index])
                        return newCommands
                    }
                ),
                safeUpdateIn(
                    ['editor', 'macrosExtra', getCurrentMacroId(state), 'breakpointIndices'],
                    (indices: any) => updateBreakpointIndices(indices || [], 'add', action.data.index + 1)
                )
            )(state)
        }
        // 插入命令
        case ActionTypes.INSERT_COMMAND: {
            return compose(
                setEditingSourceCurrent,
                updateHasUnSaved,
                setIn(
                    ['editor', 'editing', 'meta', 'selectedIndex'],
                    action.data.index
                ),
                updateIn(
                    ['editor', 'editing', 'meta', 'indexToInsertRecorded'],
                    (recordIndex: number) => {
                        if (recordIndex === undefined || recordIndex === null || recordIndex < 0) {
                            return recordIndex
                        }
                        return recordIndex + (action.data.index <= recordIndex ? 1 : 0)
                    }
                ),
                updateIn(
                    ['editor', 'editing', 'commands'],
                    (commands: any[]) => {
                        const { index, command } = action.data
                        const newCommands = commands.slice()
                        newCommands.splice(index, 0, command)
                        return newCommands
                    }
                ),
                safeUpdateIn(
                    ['editor', 'macrosExtra', getCurrentMacroId(state), 'breakpointIndices'],
                    (indices: any[]) => {
                        return updateBreakpointIndices(indices || [], 'add', action.data.index)
                    }
                )
            )(state)
        }
        // 更新命令
        case ActionTypes.UPDATE_COMMAND: {
            return compose(
                setEditingSourceCurrent,
                updateHasUnSaved,
                updateIn(
                    ['editor', 'editing', 'commands', action.data.index],
                    (cmdObj: any) => ({ ...cmdObj, ...action.data.command })
                )
            )(state)
        }
        // 更新命令
        case ActionTypes.UPDATE_COMMANDS: {
            return setIn(['editor', 'editing', 'commands'], action.data, state)
        }
        // 删除命令
        case ActionTypes.REMOVE_COMMAND: {
            return compose(
                setEditingSourceCurrent,
                updateHasUnSaved,
                (state: State) => {
                    const { commands, meta } = state.editor.editing
                    const isSelectedIndexStillValid = meta.selectedIndex >= 0 && meta.selectedIndex < commands.length

                    if (isSelectedIndexStillValid) {
                        return state
                    }

                    const startDistance = action.data.index
                    const endDistance = commands.length - action.data.index - 1
                    const nextSelectedIndex = startDistance < endDistance ? 0 : (commands.length - 1)

                    return setIn(['editor', 'editing', 'meta', 'selectedIndex'], nextSelectedIndex, state)
                },
                updateIn(
                    ['editor', 'editing', 'commands'],
                    (commands: Command[]) => {
                        const { index } = action.data
                        const newCommands = commands.slice()
                        newCommands.splice(index, 1)
                        return newCommands
                    }
                ),
                safeUpdateIn(
                    ['editor', 'macrosExtra', getCurrentMacroId(state), 'breakpointIndices'],
                    (indices: any[]) => updateBreakpointIndices(indices || [], 'delete', action.data.index)
                )
            )(state)
        }
        // 选择命令
        case ActionTypes.SELECT_COMMAND: {
            return compose(
                setIn(
                    ['editor', 'editing', 'meta', 'selectedIndex'],
                    (action.data.forceClick || state.editor.editing.meta.selectedIndex !== action.data.index)
                        ? action.data.index
                        : ''
                ),
                // Note: normalize commands whenever switching between commands in normal mode
                state.status === APP_STATUS.NORMAL
                    ? updateIn(
                        ['editor', 'editing', 'commands'],
                        (cmds: any) => cmds.map(normalizeCommand)
                    )
                    : (x: any) => x
            )(state)
        }
        // 更新编辑区
        case ActionTypes.UPDATE_EDITING:
            return compose(
                setIn(
                    ['editor', 'editing'],
                    action.data.editing
                ),
                // Note: normalize commands whenever switching between commands in normal mode
                state.status === APP_STATUS.NORMAL
                    ? updateIn(
                        ['editor', 'editing', 'commands'],
                        (cmds: any[]) => cmds.map(normalizeCommand)
                    )
                    : (x: any) => x
            )(state)
        // 剪切命令
        case ActionTypes.CUT_COMMAND: {
            const commands = action.data.indices.map((i: number) => state.editor.editing.commands[i])

            return compose(
                setEditingSourceCurrent,
                updateHasUnSaved,
                setIn(['editor', 'clipboard', 'commands'], commands),
                updateIn(
                    ['editor', 'editing', 'commands'],
                    (commands: any[]) => {
                        const newCommands = commands.slice()
                        return newCommands.filter((c, i) => action.data.indices.indexOf(i) === -1)
                    }
                ),
                safeUpdateIn(
                    ['editor', 'macrosExtra', getCurrentMacroId(state), 'breakpointIndices'],
                    (indices: number[]) => updateBreakpointIndices(indices || [], 'delete', action.data.indices)
                )
            )(state)
        }
        // 复制命令
        case ActionTypes.COPY_COMMAND: {
            const commands = action.data.indices.map((i: number) => state.editor.editing.commands[i])
            return setIn(['editor', 'clipboard', 'commands'], commands, state)
        }
        // 粘贴命令
        case ActionTypes.PASTE_COMMAND: {
            const { commands } = state.editor.clipboard

            return compose(
                setEditingSourceCurrent,
                updateHasUnSaved,
                updateIn(
                    ['editor', 'editing', 'commands'],
                    (cmds: any[]) => {
                        const newCmds = cmds.slice()
                        newCmds.splice(action.data.index + 1, 0, ...commands)
                        return newCmds
                    }
                ),
                safeUpdateIn(
                    ['editor', 'macrosExtra', getCurrentMacroId(state), 'breakpointIndices'],
                    (indices: number[]) => updateBreakpointIndices(indices || [], 'add', commands.map(_ => action.data.index + 1))
                )
            )(state)
        }
        // 移动命令
        case ActionTypes.MOVE_COMMANDS: {
            const { commands = [] } = state.editor.editing
            const { startIndex, endIndex } = action.data

            if (startIndex < 0 || startIndex >= commands.length) {
                throw new Error('startIndex is out of range')
            }

            if (endIndex < 0 || endIndex >= commands.length) {
                throw new Error('endIndex is out of range')
            }

            if (endIndex === startIndex) {
                throw new Error('startIndex and endIndex must be different')
            }

            const newCommands = [...commands]

            newCommands.splice(startIndex, 1)
            newCommands.splice(endIndex, 0, commands[startIndex])

            return compose(
                setEditingSourceCurrent,
                updateHasUnSaved,
                setIn(['editor', 'editing', 'commands'], newCommands),
                updateIn(['editor', 'editing', 'meta', 'selectedIndex'], (selectedIndex: number) => {
                    switch (selectedIndex) {
                        case startIndex: return endIndex
                        case endIndex: return startIndex
                        default: return selectedIndex
                    }
                })
            )(state)
        }
        // 规范化命令
        case ActionTypes.NORMALIZE_COMMANDS: {
            return updateIn(
                ['editor', 'editing', 'commands'],
                (cmds: any[]) => cmds.map(normalizeCommand),
                state
            )
        }
        // 更新选择的命令
        case ActionTypes.UPDATE_SELECTED_COMMAND: {
            if (state.editor.editing.meta.selectedIndex === -1) {
                return state
            }

            return compose(
                setEditingSourceCurrent,
                updateHasUnSaved,
                updateIn(
                    ['editor', 'editing', 'commands', state.editor.editing.meta.selectedIndex],
                    (cmdObj: any) => normalizeCommand({ ...cmdObj, ...action.data })
                )
            )(state)
        }
        // 编辑区保存为已存在用例
        case ActionTypes.SAVE_EDITING_AS_EXISTED: {
            return compose(
                setIn(['editor', 'editing', 'meta', 'hasUnsaved'], false),
                saveEditingSourceCurrent
            )(state)
        }
        // 编辑区保存为新用例
        case ActionTypes.SAVE_EDITING_AS_NEW: {
            return compose(
                updateIn(
                    ['editor', 'editing', 'meta'],
                    (meta: any) => ({
                        ...meta,
                        hasUnsaved: false,
                        src: pick(['id', 'name'], action.data)
                    })
                ),
                saveEditingSourceCurrent
            )(state)
        }
        // 设置测试用例
        case ActionTypes.SET_TEST_CASES: {
            return compose(
                (state: State) => {
                    const { src } = state.editor.editing.meta
                    if (!src) return state

                    const tc = state.editor.testCases.find(tc => tc.id === src.id)
                    if (!tc) return state

                    return setEditingSourceOriginalAndPure({
                        name: tc.name,
                        commands: tc.data.commands
                    }, state)
                },
                setIn(['editor', 'testCases'], action.data)
            )(state)
        }
        // 设置当前编辑的用例
        case ActionTypes.SET_EDITING: {
            // log('REDUCER SET_EDITING', action.data)

            if (!action.data) return state
            return compose(
                setEditingSourceCurrent,
                updateHasUnSaved,
                setIn(['editor', 'editing'], action.data)
            )(state)
        }
        // 编辑用例
        case ActionTypes.EDIT_TEST_CASE: {

            if (!action.data.macro) return state

            const macro = action.data.macro

            return compose(
                setIn(
                    ['editor', 'editing'],
                    {
                        ...macro.data,
                        meta: {
                            selectedIndex: -1,
                            hasUnsaved: false,
                            src: pick(['id', 'name'], macro)
                        }
                    }
                ),
                updateIn(
                    ['player'],
                    (player: any) => ({
                        ...player,
                        status: PLAYER_STATUS.STOPPED,
                        stopReason: null,
                        nextCommandIndex: null
                    })
                ),
                resetEditingSource({
                    name: macro.name,
                    commands: macro.data.commands
                }),
                updateHasUnSaved
            )(state)
        }
        // 更新一个宏的额外信息
        case ActionTypes.UPDATE_ONE_MACRO_EXTRA: {
            const { id, extra } = action.data

            if (!id) return state

            return safeUpdateIn(
                ['editor', 'macrosExtra', id],
                (data: Record<string, any>) => ({ ...data, ...extra }),
                state
            )
        }
        // 用例重命名
        case ActionTypes.RENAME_TEST_CASE:
            return setIn(['editor', 'editing', 'meta', 'src', 'name'], action.data, state)
        // 删除用例
        case ActionTypes.REMOVE_TEST_CASE: {
            if (!action.data.isCurrent) return state

            const { id } = state.editor.editing.meta.src || {}

            const candidates = state.editor.testCases.filter(tc => tc.id !== id)
            let lastIndex = state.editor.testCases.findIndex(tc => tc.id === id)
            let editing

            if (candidates.length === 0) {
                editing = { ...newTestCaseEditing }
            } else {
                const index = lastIndex === -1
                    ? 0
                    : lastIndex < candidates.length ? lastIndex : (lastIndex - 1)
                const tc = candidates[index]

                editing = {
                    ...tc.data,
                    meta: {
                        src: pick(['id', 'name'], tc),
                        hasUnsaved: false,
                        selectedIndex: index
                    }
                }
            }

            return setIn(['editor', 'editing'], editing, state)
        }
        // 编辑新用例
        case ActionTypes.EDIT_NEW_TEST_CASE: {
            return compose(
                setIn(
                    ['editor', 'editing'],
                    { ...newTestCaseEditing }
                ),
                updateIn(['player'], (player: any) => ({
                    ...player,
                    nextCommandIndex: null
                })),
                resetEditingSource({
                    name: 'Untitled',
                    commands: []
                })
            )(state)
        }
        // 设置macrosExtra
        case ActionTypes.SET_MACROS_EXTRA: {
            return setIn(['editor', 'macrosExtra'], action.data, state)
        }
        // 目录
        case ActionTypes.SET_MACRO_FOLDER_STRUCTURE: {
            return setIn(['editor', 'macroFolderStructure'], action.data, state)
        }
        // 设置播放器状态
        case ActionTypes.SET_PLAYER_STATE: {
            return compose(
                updateIn(['player'], (playerState: Record<string, any>) => ({ ...playerState, ...action.data })),
                updateIn(['noDisplayInPlay'], (noDisplayInPlay: boolean) => {
                    // Reset noDisplay to false when macro stops playing
                    return action.data.status === PLAYER_STATUS.STOPPED ? false : noDisplayInPlay
                }),
                updateIn(['ocrInDesktopMode'], (ocrInDesktopMode: boolean) => {
                    // Reset ocrInDesktopMode to false when macro stops playing
                    return action.data.status === PLAYER_STATUS.STOPPED ? false : ocrInDesktopMode
                }),
                updateIn(['replaySpeedOverrideToFastMode'], (replaySpeedOverrideToFastMode: boolean) => {
                    // Reset replaySpeedOverrideToFastMode to false when macro stops playing
                    return action.data.status === PLAYER_STATUS.STOPPED ? false : replaySpeedOverrideToFastMode
                })
            )(state)
        }
        // 增加日志
        case ActionTypes.ADD_LOGS: {
            return {
                ...state,
                logs: [...state.logs, ...action.data].slice(-500)
            }
        }
        // 清空日志
        case ActionTypes.CLEAR_LOGS: {
            return {
                ...state,
                logs: []
            }
        }
        // 增加截图
        case ActionTypes.ADD_SCREENSHOT: {
            return {
                ...state,
                screenshots: [
                    ...state.screenshots,
                    action.data
                ]
            }
        }
        // 清空截图
        case ActionTypes.CLEAR_SCREENSHOTS: {
            return {
                ...state,
                screenshots: []
            }
        }
        // 更新配置
        case ActionTypes.UPDATE_CONFIG: {
            return updateIn(
                ['config'],
                (cfg: any) => ({ ...cfg, ...action.data }),
                state
            )
        }
        // 设置CSV列表
        case ActionTypes.SET_CSV_LIST: {
            return {
                ...state,
                csvs: action.data
            }
        }
        // 设置CSV列表
        case ActionTypes.SET_SCREENSHOT_LIST: {
            return {
                ...state,
                screenshots: action.data
            }
        }
        // 设置图像库
        case ActionTypes.SET_VISION_LIST: {
            return {
                ...state,
                visions: action.data
            }
        }
        // 设置变量列表
        case ActionTypes.SET_VARIABLE_LIST: {
            return {
                ...state,
                variables: action.data
            }
        }
        // 更新UI
        case ActionTypes.UPDATE_UI: {
            return updateIn(['ui'], (ui: any) => ({ ...ui, ...action.data }), state)
        }
        // 设置视图tab
        case ActionTypes.SET_EDITOR_ACTIVE_TAB: {
            return setIn(['editor', 'activeTab'], action.data, state)
        }
        // 设置源码error
        case ActionTypes.SET_SOURCE_ERROR: {
            return setIn(['editor', 'editingSource', 'error'], action.data, state)
        }
        // 设置当前源码(编辑后的)
        case ActionTypes.SET_SOURCE_CURRENT: {
            return setIn(['editor', 'editingSource', 'current'], action.data, state)
        }
        // 更新代理
        case ActionTypes.UPDATE_PROXY: {
            return {
                ...state,
                proxy: action.data
            }
        }
        // 设置是否正在拖动命令
        case ActionTypes.SET_IS_DRAGGING_COMMAND: {
            return setIn(['editor', 'isDraggingCommand'], action.data, state)
        }
        // 设置当前宏
        case ActionTypes.SET_CURRENT_MACRO: {
            return setIn(['editor', 'currentMacro'], action.data, state)
        }
        // 设置是否正在加载
        case ActionTypes.SET_IS_LOADING_MACROS: {
            return setIn(['isLoadingMacros'], action.data, state)
        }
        // 设置来源
        case ActionTypes.SET_FROM: {
            return setIn(['from'], action.data, state)
        }
        // 设置noDisplayInPlay
        case ActionTypes.SET_NO_DISPLAY_IN_PLAY: {
            return setIn(['noDisplayInPlay'], action.data, state)
        }
        // 设置ocrInDesktopMode
        case ActionTypes.SET_OCR_IN_DESKTOP_MODE: {
            return setIn(['ocrInDesktopMode'], action.data, state)
        }
        // 设置replaySpeedOverrideToFastMode
        case ActionTypes.SET_REPLAY_SPEED_OVERRIDE_TO_FAST_MODE: {
            return setIn(['replaySpeedOverrideToFastMode'], action.data, state)
        }
        // 设置macroQuery
        case ActionTypes.SET_MACROS_QUERY: {
            return setIn(['macroQuery'], action.data, state)
        }

        // 设置indexToInsertRecorded
        case ActionTypes.SET_INDEX_TO_INSERT_RECORDED: {
            return setIn(
                ['editor', 'editing', 'meta', 'indexToInsertRecorded'],
                action.data,
                state
            )
        }
        // 设置skipOpen
        case ActionTypes.TOGGLE_RECORDER_SKIP_OPEN: {
            return updateIn(
                ['recorder', 'skipOpen'],
                (skipOpen: boolean) => action.data !== undefined ? action.data : !skipOpen,
                state
            )
        }
        default: {
            return state
        }
    }
}